using System;
using System.Collections;
using SMS.SMPPClient.Util;
using SMS.SMPPClient.PDU;

namespace SMS.SMPPClient.PDU.Outgoing
{

    ///<summary>
    /// Class to represent a PDU (protocol data unit) for a request (i.e. initiated by an
    /// ESME).
    ///</summary>
    public abstract class SMPPRequest : PDU
    {
        private static uint seqNum = 0;

        /// <summary>
        /// Indicates outcome of request.
        /// </summary>
        protected uint _CommandStatus;

        /// <summary>
        /// The command ID of this PDU.
        /// </summary>
        protected CommandIDType _CommandID;
        private TLVTable _tlvTable;

        /// <summary>
        /// The Tag, Length, Value table for this request.
        /// </summary>
        protected TLVTable tlvTable
        {
            get
            {
                return _tlvTable;
            }
        }

        /// <summary>
        /// Creates a new TLV table.
        /// </summary>
        protected SMPPRequest()
        {
            _tlvTable = new TLVTable();
        }

        /// <summary>
        /// Generates a monotonically increasing sequence number for each PDU.  When it
        /// hits the the 32 bit unsigned int maximum, it starts over.
        /// </summary>
        protected static byte[] GenerateSequenceNumber()
        {
            seqNum++;

            if (seqNum >= UInt32.MaxValue)
            {
                seqNum = 1;
            }

            byte[] temp = BitConverter.GetBytes(
                                UnsignedNumConverter.SwapByteOrdering(seqNum));

            return temp;
        }

        ///<summary>
        /// Gets the hex encoding (big-endian) of this PDU.
        ///</summary>
        ///<return>The hex-encoded version of the PDU</return>
        public abstract byte[] GetMSBHexEncoding();

        /// <summary>
        /// Creates an ArrayList consisting of the PDU header.  command ID and status
        /// need to be set before calling this.
        /// </summary>
        /// <returns>The PDU as a trimmed ArrayList.</returns>
        protected ArrayList GetPDUHeader()
        {
            //for bind_receiver
            ArrayList pdu = new ArrayList();
            pdu.AddRange(BitConverter.GetBytes(
                UnsignedNumConverter.SwapByteOrdering((uint)_CommandID)));
            pdu.AddRange(BitConverter.GetBytes(
                UnsignedNumConverter.SwapByteOrdering(_CommandStatus)));
            pdu.AddRange(GenerateSequenceNumber());

            pdu.TrimToSize();

            return pdu;
        }

        /// <summary>
        /// Calculates the length of the given ArrayList representation of the PDU and
        /// inserts this length into the appropriate spot in the PDU.  This will call
        /// TrimToSize(  ) on the ArrayList-the caller need not do it.
        /// </summary>
        /// <param name="pdu">The protocol data unit to calculate the
        /// length for.</param>
        /// <returns>The PDU with the length inserted, trimmed to size.</returns>
        protected static ArrayList InsertLengthIntoPDU(ArrayList pdu)
        {
            pdu.TrimToSize();

            uint commandLength = (uint)(4 + pdu.Count);

            uint reqLenH2N = UnsignedNumConverter.SwapByteOrdering(commandLength);

            byte[] reqLenArray = BitConverter.GetBytes(reqLenH2N);

            //insert into the PDU
            pdu.InsertRange(0, reqLenArray);

            pdu.TrimToSize();

            return pdu;
        }

        /// <summary>
        /// Takes the given PDU, calculates its length (trimming it beforehand), inserting
        /// its length, and copying it to a byte array.
        /// </summary>
        /// <param name="pdu">The PDU to encode.</param>
        /// <returns>The byte array representation of the PDU.</returns>
        protected byte[] EncodePDUForTransmission(ArrayList pdu)
        {
            AddTLVBytes(ref pdu);
            pdu = InsertLengthIntoPDU(pdu);

            byte[] result = new byte[pdu.Count];

            pdu.CopyTo(result);

            return result;
        }

        /// <summary>
        /// Copies the data from the source array, appending a null character to the end.
        /// This really only has a use for creating null terminated arrays that represent
        /// strings.
        /// </summary>
        /// <param name="source">The source array to copy from.</param>
        /// <returns></returns>
        public static byte[] ArrayCopyWithNull(byte[] source)
        {
            byte[] temp = new byte[source.Length + 1];

            for (int i = 0; i < source.Length; i++)
            {
                temp[i] = source[i];
            }

            //append the null
            temp[temp.Length - 1] = (byte)0x00;

            return temp;
        }

        /// <summary>
        /// Adds the TLV bytes to the given PDU.
        /// </summary>
        /// <param name="pdu">The PDU to add to.</param>
        protected void AddTLVBytes(ref ArrayList pdu)
        {
            ArrayList tlvs = _tlvTable.GenerateByteEncodedTLV();

            foreach (byte[] tlv in tlvs)
            {
                pdu.AddRange(tlv);
            }
        }
    }
}
